<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    <script>
      // 浅拷贝与深拷贝

      //  浅拷贝

      //  浅拷贝概念：浅拷贝是创建一个新对象，这个对象有着原始对象属性值的一份精确拷贝。只拷贝了数据对象的第一层，深层次的数据值与原始数据会互相影响。

      //             如果属性是基本类型，拷贝的就是基本类型的值，如果属性是引用类型，拷贝的就是内存地址 ，所以如果其中一个对象改变了这个地址，就会影响到另一个对象。

      // 基本数据类型值不可变。！！！！！！！都是重新赋值
      // 常用浅拷贝方式：Object.assign()、扩展运算符

      // 例1：
      // let a = '1'
      // let b = a
      // b = '2'
      // console.log(a, 'a') //1  // 基本类型的拷贝，拷贝的就是基本类型的值，只有一层所以可以直接存在在新内存中，不会影响原数据

      // 例2：
      // let obj = {
      //   a: '1',
      //   b: {
      //     c: '2',
      //   },
      // }
      // let obj2 = obj
      // obj2.b.c = '22'
      // console.log(obj, 'obj') //obj2改变对象值 发现obj也改变了

      // 例3： 赋值（=）和浅拷贝的区别？
      // var obj1 = {
      //   name: '张三',
      //   age: '18',
      //   language: [1, [2, 3], [4, 5]],
      // }
      // var obj2 = obj1
      // var obj3 = shallowCopy(obj1)
      // function shallowCopy(src) {
      //   var dst = {}
      //   for (var prop in src) {
      //     if (src.hasOwnProperty(prop)) {
      //       dst[prop] = src[prop]
      //     }
      //   }
      //   return dst
      // }
      // obj2.name = '李四'
      // obj2.age = '20'
      // obj2.language[1] = [11]
      // console.log(obj1, 'obj1')
      // console.log(obj2, 'obj2')
      // 赋值：
      // obj2为obj1赋值所得，可以看出不管里面的是单一变量 （基本数据类型）name:'张三'，或者复杂数据类型（引用类型）language: [1, [2, 3], [4, 5]] ； obj1都会因为obj2的改变而改变，
      // 因为这里obj1和obj2,都是栈中的地址，都指向同一个堆 （数据源）所以不管谁改变 这2个都会联动改变。（前提是obj1本身就是引用类型）

      // obj3.name = '赵5'
      // obj3.age = '21'
      // obj3.language[2] = '嘎嘎'
      // console.log(obj1, 'obj1')
      // console.log(obj3, 'obj3')
      // 浅拷贝：
      // obj3是obj1浅拷贝而来，所谓浅拷贝将 obj1对象拷贝到 obj3 对象中，但不包括obj1 里面的子对象(或者复杂数据类型)，这里的name和age都是字符串类型属于基本数据类型，浅拷贝后为独立的一个新存在的地址
      // 所以2个对象的基本数据类型的变量互相不影响，但是language是复杂数据类型，里面的数组为多层，没有进行再次拷贝，所以还是共用的一个栈地址language，因此改变后，两个对象里的language都会变动。
      // ----------------------------------
      // ----------------------------------
      // ----------------------------------

      // 总而言之，浅拷贝只复制指向某个对象的指针（地址ip），而不复制对象本身，新旧对象还是共享同一块内存。但深拷贝会另外创造一个一模一样的对象，新对象跟原对象不共享内存，修改新对象不会改到原对象。

      //  基础数据类型（简单类型）：   存储于栈内存中，数据大小确定，按值存放，所以能够直接访问
      //  复杂数据类型（引用类型）：   真实数据存储于堆内存中，数据大小不确定，声明的变量（let obj={}）保存的是栈内存中的指针（引用地址 或者叫ip,这里就是声明的obj），该指针指向的是堆内存中存放的数据地址 值是可变的

      // 当我们把一个对象赋值给一个新的变量时，赋的其实是该对象的在栈中的地址，而不是堆中的真实数据。也就是两个对象指向的是同一个存储空间，无论哪个对象发生改变，其实都是改变的存储空间的内容，因此，两个对象是联动的。

      //  深拷贝  (深拷贝是对对象以及对象的所有子对象进行拷贝)
      // 引用类型是可以直接改变其值的 如 let a=[1,2,3] a.push(1),改变原数组

      // 深拷贝的方法：
      // 1.JSON.parse(JSON.stringify())
      // var obj1 = {
      //   name: '张三',
      //   son: {
      //     name: 'xixi',
      //     age: '5',
      //   },
      //   language: [1, [2, 3], [4, 5]],
      //   speak() {
      //     console.log('I can speak')
      //   },
      // }
      // let obj2 = JSON.parse(JSON.stringify(obj1))
      // obj2.name = '李四'
      // obj2.son.age = '10'
      // obj2.language[0] = '10'
      // console.log(obj1, 'obj1')
      // console.log(obj2, 'obj2')
      // 总结：可简便快捷实现深拷贝，缺点是对象中如果有函数，构造函数，正则或者undefined时，拷贝会丢失。

      // 2.函数库lodash的_.cloneDeep方法 （需引入包）
      // var _ = require('lodash')
      // var obj1 = {
      //   a: 1,
      //   b: { f: { g: 1 } },
      //   c: [1, 2, 3],
      // }
      // var obj2 = _.cloneDeep(obj1)
      // console.log(obj1.b.f === obj2.b.f) // false

      // 3.jQuery.extend()方法
      // $.extend(deepCopy, target, object1, [objectN])//第一个参数为true,就是深拷贝

      // 4.手写暴力递归深拷贝（递归方法实现深度克隆原理：遍历对象、数组直到里边都是基本数据类型，然后再去复制，就是深度拷贝。）
      //实现深拷贝函数

      var obj1 = {
        name: '张三',
        son: {
          name: 'xixi',
          age: '5',
        },
        language: [1, [2, 3], [4, 5]],
        speak() {
          console.log('I can speak')
        },
      }
      let deepClone = (data) => {
        // 判断数据类型,每次遍历之后有一种类型。
        const type = getType(data)
        // 递归生成新的数据存放
        let obj = null
        console.log(type, 'type')
        switch (type) {
          case 'object':
            obj = {}
            for (let i in data) {
              if (data.hasOwnProperty(i)) {
                // 只要原型上有这个属性就重新赋值并且把这个值进行递归
                obj[i] = deepClone(data[i])
              }
            }
            break
          case 'array':
            obj = []
            for (let i = 0; i < data.length; i++) {
              if (data[i].length > 0) {
                obj.push(deepClone(data[i]))
              } else {
                obj.push(data[i])
              }
            }
            break
          default:
            // 这里只简单处理2种类型数据 其它类型数据可以再划分处理
            return data
        }

        return obj
      }
      let getType = (data) => {
        // 设置数据的基本类型
        const map = {
          '[object Boolean]': 'boolean',
          '[object Number]': 'number',
          '[object String]': 'string',
          '[object Function]': 'function',
          '[object Array]': 'array',
          '[object Date]': 'date',
          '[object RegExp]': 'regExp',
          '[object Undefined]': 'undefined',
          '[object Null]': 'null',
          '[object Object]': 'object',
          '[object HTMLDivElement]': 'element',
          // document.getElementById('div')
        }
        return map[Object.prototype.toString.apply(data)]
      }
      let newObj = deepClone(obj1)

      newObj.name = '赵18'
      newObj.language = [1, 2, 3]

      console.log(newObj, 'newObj')
      console.log(obj1, 'obj1')
      // 可以看出新对象和原对象互不影响,完成深拷贝

      // ********************************************************************
      // ********************************************************************
      // ********************************************************************
      // ********************************************************************
      // ********************************************************************
      // ********************************************************************
      // ********************************************************************
      // ********************************************************************
      // ********************************************************************
      // ********************************************************************
      // ********************************************************************
      // ********************************************************************
      // ********************************************************************
      // ********************************************************************
      // ********************************************************************

      // JavaScript 数据结构:

      // 数据结构的本质就在于如何将现实世界中各种各样的数据放入到内存中，并且如何在内存中操作这些数据。
      // 内存的最基本存储单位叫做“存储单元”，把“存储单元”想象成一个盒子并且每个盒子有一个编号叫做内存地址。
      // 数据就是一个一个的放入存储单元中，数据可以一个一个挨着放置，也可以随意放置。我们称之为数据存储方式。

      // 数据结构分为三大类：

      // 1.线性表: 数组就是最简单粗暴的存储方法。直接拉出一大块数据存在那里。数组的快速存取其实只是一个副作用，因为所有的数据都在一起，可以直接算出来数据的地址。

      // 链表则是为了解决可以无限增长的需求,未知数据量的的大小而产生。最后采用的方法就是从入口开始，每一个数据块不仅仅有数据，还会有指向下一个数据块的线索，用来寻找下一个数据。这就是链表。
      // 所谓的双向链表，只是加了一个向前的线索的链表而已。不仅如此队列、栈，都是线性表的特殊形态，进行了操作上的限制罢了，既可以是数组也可以是链表。

      // 2.树 :  树是为了解决单一入口下的非线性关联性的数据存储或者排序功能而来的。 本质上，树相对于链表，就是每个节点不止有一个后续节点但是只有一个前置节点。

      // 3.图:  图的本质其实就是把非线性表进一步扩展，每个节点会有不止一个前置和后缀节点。 它们的演变过程是：线性表 => 树 => 图 。

      // ***********************************************************

      // 数组 ：最简单的数据结构，缺点是数组的大小是固定的，从数组的起点或中间插入或移除项的成本很高，因为需要移动元素。

      // 链表 ：链表存储有序的元素集合，但不同于数组，链表中的元素在内存中并不是连续放置的。每个元素由一个存储元素本身的节点和一个指向下一个元素的引用（也称指针或链接）组成。

      // 特征
      // 链表中元素是散落在内存的不同位置，通过指针使得它们互相产生联系的，因此当需要访问链表中某个元素时需要从头开始遍历链表，查询成本高。
      // 由于链表是通过next指针时前后元素产生关联，因此插入和删除一个元素只需要改变next指针的指向，并不需像数组那样挪动每个元素，因此插入和删除是非常高效的。
      // let listNode = {
      //   val: 1,
      //   next: {
      //     val: 2,
      //     next: {
      //       val: 3,
      //       next: {},
      //     },
      //   },
      // }
      // 创建链表
      // function LinkedList() {
      //   // Node类表示要加入列表的项。它包含一个element属性，即要添加到列表的值，以及一个next属性，即指向列表中下一个节点项的指针
      //   var Node = function (element) {
      //     this.element = element
      //     this.next = null
      //   }
      //   var length = 0
      //   var head = null // 我们还需要存储第一个节点的引用
      //   this.append = function (element) {} // 向列表尾部添加一个新的项。
      //   this.insert = function (position, element) {} // 向列表的特定位置插入一个新的项。
      //   this.removeAt = function (position) {} // 从列表的特定位置移除一项。
      //   this.remove = function (element) {} // 从列表中移除一项。
      //   this.indexOf = function (element) {} // 返回元素在列表中的索引。如果列表中没有该元素则返回-1。
      //   this.isEmpty = function () {} // 如果链表中不包含任何元素，返回true，如果链表长度大于0则返回false。
      //   this.size = function () {} // 返回链表包含的元素个数。与数组的length属性类似。
      // }
      // var list = new LinkedList()
      // list.append(15)

      // 树： 二叉树，BST二叉树   https://juejin.cn/post/6844904161557872653#heading-15
    </script>
  </body>
</html>
